package eu.codlab.game.gameba;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.os.Environment;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Toast;

import java.io.IOException;
import java.util.ArrayList;

import eu.codlab.src.game.R;

import eu.codlab.androidemu.gbcdroid.wrapper.Wrapper;
import eu.codlab.game.gameba.EmulatorView.OnFrameDrawnListener;
import eu.codlab.game.gameba.nonem.input.AbstractButtonListener;
import eu.codlab.game.gameba.nonem.input.AbstractButtonServer;
import eu.codlab.game.gameba.nonem.input.GameKeyListener;

public class VirtualKeypad
implements View.OnTouchListener, AbstractButtonListener, Callback, OnFrameDrawnListener{

	private static final int DPAD_4WAY[] = {
		Emulator.GAMEPAD_LEFT,
		Emulator.GAMEPAD_UP,
		Emulator.GAMEPAD_RIGHT,
		Emulator.GAMEPAD_DOWN
	};

	private static final float DPAD_DEADZONE_VALUES[] = {
		0.1f, 0.14f, 0.1667f, 0.2f, 0.25f,
	};

	private Context context;
	private EmulatorViewControl view;
	private float scaleX;
	private float scaleY;
	private int transparency;

	private GameKeyListener gameKeyListener;
	private int keyStates;
	private Vibrator vibrator;
	private boolean vibratorEnabled;
	private boolean dpad4Way;
	private float dpadDeadZone = DPAD_DEADZONE_VALUES[2];
	private float pointSizeThreshold;
	private boolean inBetweenPress;

	private ArrayList<Control> controls = new ArrayList<Control>();
	private Control dpad;
	private Control buttons;
	private Control extraButtons;
	private Control selectStart;
	private Control left;
	private Control right;

	//private Emulator emulator = Emulator.getInstance();
	public VirtualKeypad(Context gameBoidManager) {
		//view = this;
		this.context = gameBoidManager;
		vibrator = (Vibrator) context.getSystemService(
				Context.VIBRATOR_SERVICE);
		dpad = createControl(R.drawable.dpad);
		buttons = createControl(R.drawable.buttons);
		extraButtons = createControl(R.drawable.extra_buttons);
		selectStart = createControl(R.drawable.select_start_buttons);
		left = createControl(R.drawable.button_l);
		right = createControl(R.drawable.button_r);

		/*final SurfaceHolder holder = getHolder();
		holder.setFormat(PixelFormat.RGB_565);
		holder.setKeepScreenOn(true);*/

	}

	public void setSurfaceView(EmulatorViewControl emulatorViewControl){
		view = emulatorViewControl;
	}
	public void setGameKeyListener(EmulatorViewControl emulatorView2, GameKeyListener l) {
		setSurfaceView(emulatorView2);
		gameKeyListener = l;

	}

	public void setGameKeyListener(GameKeyListener listener) {
		gameKeyListener = listener;

	}

	public final int getKeyStates() {
		return keyStates;
	}

	public void reset() {
		keyStates = 0;
	}

	public final void destroy() {
	}

	public final void resize(int w, int h) {
		//TODO implements settings
		SharedPreferences prefs = PreferenceManager.
				getDefaultSharedPreferences(context);
		vibratorEnabled = prefs.getBoolean("enableVibrator", true);
		dpad4Way = true;//prefs.getBoolean("dpad4Way", false);

		int value = 2;//prefs.getInt("dpadDeadZone", 2);
		value = (value < 0 ? 0 : (value > 4 ? 4 : value));
		dpadDeadZone = DPAD_DEADZONE_VALUES[value];

		inBetweenPress = false;// prefs.getBoolean("inBetweenPress", false);

		pointSizeThreshold = 1.0f;
		if (false){//prefs.getBoolean("pointSizePress", false)) {
			int threshold = 7;//prefs.getInt("pointSizePressThreshold", 7);
			pointSizeThreshold = (threshold / 10.0f) - 0.01f;
		}

		/*dpad.hide(prefs.getBoolean("hideDpad", false));
		buttons.hide(prefs.getBoolean("hideButtons", false));*/
		extraButtons.hide(true);//.getBoolean("hideExtraButtons", false));
		extraButtons.disable(true);//prefs.getBoolean("disableExtraButtons", true));
		/*selectStart.hide(prefs.getBoolean("hideSelectStart", false));
		left.hide(prefs.getBoolean("hideLeft", false));
		right.hide(prefs.getBoolean("hideRight", false));*/

		scaleX = (float) w / view.getWidth();
		scaleY = (float) h / view.getHeight();

		float controlScale = getControlScale(null);//prefs);
		float sx = scaleX * controlScale;
		float sy = scaleY * controlScale;
		Resources res = context.getResources();
		for (Control c : controls){
			c.load(res, sx, sy);
		}

		final int margin = 2*10;//prefs.getInt("layoutMargin", 2) * 10;
		int marginX = (int) (margin * scaleX);
		int marginY = (int) (margin * scaleY);
		reposition(w - marginX, h - marginY, null);//prefs);

		transparency = 50;//prefs.getInt("vkeypadTransparency", 50);

		/*try{
			Canvas c = view.getHolder().lockCanvas();
			if(c!= null){
				this.draw(c);
				view.getHolder().unlockCanvasAndPost(c);
			}
		}catch(Exception e){

		}*/
	}

	public void draw(Canvas canvas) {
		Paint paint = new Paint();
		paint.setAlpha(transparency * 2 + 30);

		for (Control c : controls){
			//Log.d("drawing","drawing");
			c.draw(canvas, paint);
		}
	}

	private static float getControlScale(SharedPreferences prefs) {
		String value = null;//prefs.getString("vkeypadSize", null);
		if ("small".equals(value))
			return 1.0f;
		if ("large".equals(value))
			return 1.33333f;
		return 1.2f;
	}

	private Control createControl(int resId) {
		Control c = new Control(resId);
		controls.add(c);
		return c;
	}

	private void makeBottomBottom(int w, int h) {
		/*if (dpad.getWidth() + buttons.getWidth() > w) {
			makeBottomTop(w, h);
			return;
		}*/
		//Log.d("make","bottombottom");
		left.move(0, 0);
		right.move(w - right.getWidth(), 0);
		dpad.move(0, h - dpad.getHeight());
		buttons.move(w - buttons.getWidth(), h - buttons.getHeight());
		if (extraButtons.isEnabled()) {
			extraButtons.move(w - buttons.getWidth(),
					h - buttons.getHeight() * 7 / 3);
		}

		int x = (w + dpad.getWidth() - buttons.getWidth() -
				selectStart.getWidth()) / 2;
		if (x > dpad.getWidth())
			selectStart.move(x, h - selectStart.getHeight());
		else {
			x = (w - selectStart.getWidth()) / 2;
			selectStart.move(x, right.getHeight());
		}
	}

	public void reposition(int w, int h, SharedPreferences prefs) {
		makeBottomBottom(w, h);
	}

	private boolean shouldVibrate(int oldStates, int newStates) {
		return (((oldStates ^ newStates) & newStates) != 0);
	}

	private void setKeyStates(int newStates) {
		if (keyStates == newStates)
			return;

		if (vibratorEnabled && shouldVibrate(keyStates, newStates))
			vibrator.vibrate(33);

		keyStates = newStates;
		gameKeyListener.onGameKeyChanged();
	}

	private int get4WayDirection(float x, float y) {
		x -= 0.5f;
		y -= 0.5f;

		if (Math.abs(x) >= Math.abs(y))
			return (x < 0.0f ? 0 : 2);
		return (y < 0.0f ? 1 : 3);
	}

	private int getDpadStates(float x, float y) {
		if (dpad4Way)
			return DPAD_4WAY[get4WayDirection(x, y)];

		final float cx = 0.5f;
		final float cy = 0.5f;
		int states = 0;

		if (x < cx - dpadDeadZone)
			states |= Emulator.GAMEPAD_LEFT;
		else if (x > cx + dpadDeadZone)
			states |= Emulator.GAMEPAD_RIGHT;
		if (y < cy - dpadDeadZone)
			states |= Emulator.GAMEPAD_UP;
		else if (y > cy + dpadDeadZone)
			states |= Emulator.GAMEPAD_DOWN;

		return states;
	}

	private int getButtonsStates(int[] buttons,
			float x, float y, float size) {

		if (size > pointSizeThreshold)
			return (buttons[0] | buttons[1]);

		if (inBetweenPress) {
			int states = 0;
			if (x < 0.58f)
				states |= buttons[0];
			if (x > 0.42f)
				states |= buttons[1];
			return states;
		}
		return (x < 0.5f ? buttons[0] : buttons[1]);
	}

	private int getRightStates(float x, float y) {
		return Emulator.GAMEPAD_TR;
	}
	private int getLeftStates(float x, float y) {
		return Emulator.GAMEPAD_TL;
	}
	private int getSelectStartStates(float x, float y) {
		return (x < 0.5f ? Emulator.GAMEPAD_SELECT : Emulator.GAMEPAD_START);
	}

	private float getEventX(MotionEvent event, int index, boolean flip) {
		float x = Wrapper.MotionEvent_getX(event, index);
		if (flip)
			x = view.getWidth() - x;
		return (x * scaleX);
	}

	private float getEventY(MotionEvent event, int index, boolean flip) {
		float y = Wrapper.MotionEvent_getY(event, index);
		if (flip)
			y = view.getHeight() - y;
		return y * scaleY;
	}

	private Control findControl(float x, float y) {
		for (Control c : controls) {
			if (c.hitTest(x, y))
				return c;
		}
		return null;
	}

	private static final int[] BUTTONS = {
		Emulator.GAMEPAD_B, Emulator.GAMEPAD_A
	};

	private static final int[] EXTRA_BUTTONS = {
		Emulator.GAMEPAD_B_TURBO, Emulator.GAMEPAD_A_TURBO
	};

	public int getSpaceHeight(){
		return left != null && this.dpad != null ? left.getHeight()+dpad.getHeight() : 0;
	}

	public int getSpaceWidth(){
		return left != null && right != null ? left.getWidth()+right.getWidth() : 0;
	}

	private int getControlStates(Control c, float x, float y, float size) {
		x = (x - c.getX()) / c.getWidth();
		y = (y - c.getY()) / c.getHeight();

		if (c == dpad)
			return getDpadStates(x, y);
		if (c == left)
			return getLeftStates(x, y);
		if (c == right)
			return getRightStates(x, y);
		if (c == buttons)
			return getButtonsStates(BUTTONS, x, y, size);
		if (c == extraButtons)
			return getButtonsStates(EXTRA_BUTTONS, x, y, size);
		if (c == selectStart)
			return getSelectStartStates(x, y);

		return 0;
	}

	public boolean onTouch(MotionEvent event, boolean flip) {
		int action = event.getAction();
		int id;
		int pointerUpId = -1;

		switch (action ){//& MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			setKeyStates(0);
			return true;

		case MotionEvent.ACTION_DOWN:
			//case MotionEvent.ACTION_POINTER_DOWN:
		case MotionEvent.ACTION_MOVE:
		case MotionEvent.ACTION_OUTSIDE:
			break;
		default:
			return false;
		}

		int states = 0;
		int n = Wrapper.MotionEvent_getPointerCount(event);//event.getPointerCount();
		for (int i = 0; i < n; i++) {
			float x = getEventX(event, i, flip);
			float y = getEventY(event, i, flip);
			Control c = findControl(x, y);
			if (c != null) {
				states |= getControlStates(c, x, y,
						event.getSize(i));
			}
		}
		setKeyStates(states);
		return true;
	}


	private static class Control {
		private int resId;
		private boolean hidden;
		private boolean disabled;
		private Bitmap bitmap;
		private RectF bounds = new RectF();

		Control(int r) {
			resId = r;
		}

		final float getX() {
			return bounds.left;
		}

		final float getY() {
			return bounds.top;
		}

		final int getWidth() {
			return bitmap != null ? bitmap.getWidth() : 0;
		}

		final int getHeight() {
			return bitmap != null ? bitmap.getHeight() : 0;
		}

		final boolean isEnabled() {
			return !disabled;
		}

		final void hide(boolean b) {
			hidden = b;
		}

		final void disable(boolean b) {
			disabled = b;
		}

		final boolean hitTest(float x, float y) {
			return bounds.contains(x, y);
		}

		final void move(float x, float y) {
			//Log.d("moveing",""+x+" "+y);
			bounds.set(x, y, x + bitmap.getWidth(), y + bitmap.getHeight());
		}

		final void load(Resources res, float sx, float sy) {
			bitmap = ((BitmapDrawable) res.getDrawable(resId)).getBitmap();
			bitmap = Bitmap.createScaledBitmap(bitmap,
					(int) (sx * bitmap.getWidth()),
					(int) (sy * bitmap.getHeight()), true);
		}

		final void reload(Resources res, int id) {
			int w = bitmap.getWidth();
			int h = bitmap.getHeight();
			bitmap = ((BitmapDrawable) res.getDrawable(id)).getBitmap();
			bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
		}

		final void draw(Canvas canvas, Paint paint) {
			//if(resId == R.drawable.buttons)
			//Log.d("Drawing",bounds.left+" "+bounds.top);
			if (!hidden && !disabled)
				canvas.drawBitmap(bitmap, bounds.left, bounds.top, paint);
		}
	}


	@Override
	public void onKeyFromClient(AbstractButtonServer server, int keycode,
			int state) {
		if(state == 1){
			switch(keycode){
			case Emulator.GAMEPAD_A:
				keyStates |= Emulator.GAMEPAD_A;
				break;
			case Emulator.GAMEPAD_B:
				keyStates |= Emulator.GAMEPAD_B;
				break;
			case Emulator.GAMEPAD_TR:
				keyStates |= Emulator.GAMEPAD_TR;
				break;
			case Emulator.GAMEPAD_TL:
				keyStates |= Emulator.GAMEPAD_TL;
				break;
			case Emulator.GAMEPAD_SELECT:
				keyStates |= Emulator.GAMEPAD_SELECT;
				break;
			case Emulator.GAMEPAD_A_TURBO:
				keyStates |= Emulator.GAMEPAD_A_TURBO;
				break;
			case Emulator.GAMEPAD_B_TURBO:
				keyStates |= Emulator.GAMEPAD_B_TURBO;
				break;
			case Emulator.GAMEPAD_START:
				keyStates |= Emulator.GAMEPAD_START;
				break;
			default:
				return;
			}
		}else if(state == 0){
			switch(keycode){
			case Emulator.GAMEPAD_A:
				keyStates &= ~Emulator.GAMEPAD_A;
				break;
			case Emulator.GAMEPAD_B:
				keyStates &= ~Emulator.GAMEPAD_B;
				break;
			case Emulator.GAMEPAD_TR:
				keyStates &= ~Emulator.GAMEPAD_TR;
				break;
			case Emulator.GAMEPAD_TL:
				keyStates &= ~Emulator.GAMEPAD_TL;
				break;
			case Emulator.GAMEPAD_SELECT:
				keyStates &= ~Emulator.GAMEPAD_SELECT;
				break;
			case Emulator.GAMEPAD_START:
				keyStates &= ~Emulator.GAMEPAD_START;
				break;
			case Emulator.GAMEPAD_A_TURBO:
				keyStates &= Emulator.GAMEPAD_A_TURBO;
				break;
			case Emulator.GAMEPAD_B_TURBO:
				keyStates &= Emulator.GAMEPAD_B_TURBO;
				break;
			default:
				return;
			}
		}else if(state == 2){
			keyStates = keycode;
		}
		gameKeyListener.onGameKeyChanged();
		return;
	}

	@Override
	public boolean onTouch(View arg0, MotionEvent arg1) {
		return onTouch(arg1, false);
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder,
			int format, int width, int height) {
		this.resize(width, height);
	}

	@Override
	public void surfaceCreated(SurfaceHolder arg0) {
		this.resize(view.getWidth(), view.getHeight());
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder arg0) {
		this.destroy();
	}

	int t = 0;
	@Override
	public void onFrameDrawn(Canvas canvas1) {
		//Canvas canvas = view.getHolder().lockCanvas();
		draw(canvas1);
		//view.getHolder().unlockCanvasAndPost(canvas);
	}

}
